package com.opages.platform.extension;

import com.opages.platform.exception.ExtensionServiceNotFoundException;
import com.opages.platform.model.ExtensionHolder;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Predicate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 扩展加载器
 * @author daizhong.liu
 * @create 2021-10-20 18:51
 **/
public class ExtensionLoader {
    private static final Logger LOGGER = LoggerFactory.getLogger(ExtensionLoader.class);
    private static final String SERVICES_DIRECTORY = "META-INF/services/";
    private static final String OPAGES_DIRECTORY = "META-INF/opages/";
    private static final String rexBracket = "\\(([^}]*)\\)";
    private static final ConcurrentMap<Class<?>, List<ExtensionHolder>> EXTENSION_INSTANCES = new ConcurrentHashMap<>();
    private static ExtensionFactory extensionFactory;

    public static <T> T getExtension(Class<T> clazz) {
        return getExtension(clazz,null,null,null);
    }

    public static <T> T getExtension(Class<T> clazz, String serverName) {
        return getExtension(clazz,serverName,null,null);
    }

    public static <T> T getExtension(Class<T> clazz, String serverName, Class[] argTypes, Object[] args) {
        if (clazz == null) {
            throw new IllegalArgumentException("Extension type == null");
        }

        if(extensionFactory == null) {
            synchronized (ExtensionFactory.class) {
                if(extensionFactory == null) {
                    extensionFactory = loadFile(ExtensionFactory.class, null, findClassLoader(), null, null);
                }
            }
        }

        if(clazz == ExtensionFactory.class) {
            return (T)extensionFactory;
        }
        return extensionFactory.getExtension(clazz,serverName,argTypes,args);
    }


    static <S> S loadFile(Class<S> service, String serverName, ClassLoader loader, Class[] argTypes,
                         Object[] args) {
        try {
            boolean foundFromCache = true;
            List<ExtensionHolder> extensionHolders = EXTENSION_INSTANCES.get(service);
            if (extensionHolders == null) {
                synchronized (service) {
                    extensionHolders = EXTENSION_INSTANCES.get(service);
                    if (extensionHolders == null) {
                        if(loader == null) {
                            loader = findClassLoader();
                        }
                        extensionHolders = findAllExtensionClass(service, loader);
                        foundFromCache = false;
                        EXTENSION_INSTANCES.put(service, extensionHolders);
                    }
                }
            }
            if (extensionHolders.isEmpty()) {
                throw new ExtensionServiceNotFoundException("Not found service provider (" + service + (StringUtils.isNotEmpty(serverName) ?
                        "[" + service.getSimpleName() +"]" : ")") +" and classloader (" + ObjectUtils.toString(loader)+")");
            }
            ExtensionHolder extensionHolder = null;
            if(StringUtils.isNotEmpty(serverName)) {
                extensionHolder = extensionHolders.stream().filter(new Predicate<ExtensionHolder>() {
                    @Override
                    public boolean test(ExtensionHolder extensionHolder) {
                        if (serverName.equals(extensionHolder.getName())) {
                            return true;
                        }
                        return false;
                    }
                }).findFirst().get();
            }else {
                extensionHolder = extensionHolders.get(extensionHolders.size() - 1);

            }
            if(extensionHolder != null) {
                Object obj = extensionHolder.getObj();
                if(obj == null) {
                    obj = initInstance(service,extensionHolder.getClazz(),argTypes,args);
                    extensionHolder.setObj(obj);
                }
            }else {
                throw new ExtensionServiceNotFoundException(
                        "Not found service provider(" + service + (StringUtils.isNotEmpty(serverName) ?
                                "[" + service.getSimpleName() +"]" : ")") +" and classloader : (" + ObjectUtils.toString(loader)+")");
            }
            if (!foundFromCache && LOGGER.isInfoEnabled()) {
                LOGGER.info("load " + service.getSimpleName() + "[" + serverName + "] extension by class[" + extensionHolder.getClazz()
                        .getName() + "]");
            }
            return (S)extensionHolder.getObj();
        } catch (Throwable e) {
            if (e instanceof ExtensionServiceNotFoundException) {
                throw (ExtensionServiceNotFoundException)e;
            } else {
                e.printStackTrace();
                return null;
            }
        }
    }

    private static <S> List<ExtensionHolder> findAllExtensionClass(Class<S> service, ClassLoader loader) {
        List<ExtensionHolder> extensions = new ArrayList<ExtensionHolder>();
        try {
            loadFile(service, SERVICES_DIRECTORY, loader, extensions);
            loadFile(service, OPAGES_DIRECTORY, loader, extensions);
        } catch (IOException e) {
            throw new ExtensionServiceNotFoundException(e);
        }

        if (extensions.isEmpty()) {
            return extensions;
        }
        Collections.sort(extensions, new Comparator<ExtensionHolder>() {
            @Override
            public int compare(ExtensionHolder e1, ExtensionHolder e2) {
                int o1 = 0;
                int o2 = 0;
                if (e1 != null) {
                    o1 = e1.getOrder();
                }

                if (e2 != null) {
                    o2 = e2.getOrder();
                }

                return Integer.compare(o1, o2);

            }
        });

        return extensions;
    }

    private static <S> void loadFile(Class<S> service, String dir, ClassLoader classLoader, List<ExtensionHolder> extensions)
            throws IOException {
        String fileName = dir + service.getName();
        Enumeration<URL> urls;
        if (classLoader != null) {
            urls = classLoader.getResources(fileName);
        } else {
            urls = ClassLoader.getSystemResources(fileName);
        }
        if (urls != null) {
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                BufferedReader reader = null;
                try {
                    reader = new BufferedReader(new InputStreamReader(url.openStream(), "UTF-8"));
                    String line = null;
                    while ((line = reader.readLine()) != null) {
                        final int ci = line.indexOf('#');
                        if (ci >= 0) {
                            line = line.substring(0, ci);
                        }
                        line = line.trim();
                        if (line.length() > 0) {
                            extensions.add(parseExtension(service,classLoader, line));
                        }
                    }
                } catch (Throwable e) {
                    LOGGER.warn(e.getMessage());
                } finally {
                    try {
                        if (reader != null) {
                            reader.close();
                        }
                    } catch (IOException ioe) {
                    }
                }
            }
        }
    }

    /**
     * 实例化
     * @param <S>       泛型接口
     * @param service   接口
     * @param implClazz 实现
     * @param argTypes  参数类型
     * @param args      参数
     * @return s 实例
     * @throws IllegalAccessException the illegal access exception
     * @throws InstantiationException the instantiation exception
     * @throws NoSuchMethodException the no such method exception
     * @throws InvocationTargetException the invocation target exception
     */
    protected static <S> S initInstance(Class<S> service, Class<S> implClazz, Class[] argTypes, Object[] args)
            throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        S s = null;
        if (argTypes != null && args != null) {
            // Constructor with arguments
            Constructor<S> constructor = implClazz.getDeclaredConstructor(argTypes);
            constructor.setAccessible(true);
            s = service.cast(constructor.newInstance(args));
        } else {
            // default Constructor
            s = service.cast(implClazz.newInstance());
        }
        return s;
    }

    private static <S> ExtensionHolder parseExtension(Class<S> service,ClassLoader classLoader,String line) {
        String name = null;
        ExtensionHolder holder = null;
        int i = line.indexOf('=');
        int order = 0;
        if (i != -1) {
            //spiExtensionFactory(1)=com.opages.platform.extension.SpiExtensionFactory
            name = line.substring(0, i).trim();
            if(name.indexOf("(") != -1) {
                Pattern regex = Pattern.compile(rexBracket);
                Matcher matcher = regex.matcher(name);
                name = name.substring(0,name.indexOf("("));
                if(matcher.find()) {
                    String orderContent = matcher.group(1);
                    order = Integer.parseInt(orderContent);
                }
            }
            line = line.substring(i + 1).trim();//扩展全类名
        } else {
            name = lookupBeanName(line);
        }
        try {
            Class<?> clz = Class.forName(line, true, classLoader);
            holder = new ExtensionHolder(name,order,clz);
            if (!service.isAssignableFrom(clz)) { //扩展实现类是否实现了扩展接口type
                throw new IllegalStateException("Error when load extension class(interface: " +
                        service + ", class line: " + clz.getName() + "), class "
                        + clz.getName() + "is not subtype of interface.");
            }
        } catch (LinkageError | ClassNotFoundException e) {
            LOGGER.warn("load [{}] class fail. {}", line, e.getMessage());
        }
        return holder;
    }

    public static String lookupBeanName(String content) {
        String beanName = content;
        int i = content.lastIndexOf(".");
        if(i != -1) {
            String className = content.substring(i+1);
            beanName = className.substring(0,1).toLowerCase()+ className.substring(1);
        }
        return beanName;
    }

    private static ClassLoader findClassLoader() {
        return ExtensionLoader.class.getClassLoader();
    }
}
